#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <net/if.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>

// from https://gist.github.com/ccbrown/9722406
void DumpHex(const void *data, size_t size)
{
	char ascii[17];
	size_t i, j;
	ascii[16] = '\0';
	for (i = 0; i < size; ++i) {
		printf("%02X ", ((unsigned char *)data)[i]);
		if (((unsigned char *)data)[i] >= ' ' && ((unsigned char *)data)[i] <= '~') {
			ascii[i % 16] = ((unsigned char *)data)[i];
		} else {
			ascii[i % 16] = '.';
		}
		if ((i + 1) % 8 == 0 || i + 1 == size) {
			printf(" ");
			if ((i + 1) % 16 == 0) {
				printf("|  %s \n", ascii);
			} else if (i + 1 == size) {
				ascii[(i + 1) % 16] = '\0';
				if ((i + 1) % 16 <= 8) {
					printf(" ");
				}
				for (j = (i + 1) % 16; j < 16; ++j) {
					printf("   ");
				}
				printf("|  %s \n", ascii);
			}
		}
	}
}

void print_netlink_header(struct nlmsghdr *hdr)
{
	printf("Netlink header:\n");
	printf("    nlmsg_len: %d\n", hdr->nlmsg_len);
	printf("    nlmsg_type: %d\n", hdr->nlmsg_type);
	printf("    nlmsg_flags: %d\n", hdr->nlmsg_flags);
	printf("    nlmsg_seq: %d\n", hdr->nlmsg_seq);
	printf("    nlmsg_pid: %d\n", hdr->nlmsg_pid);
}

//http://lxr.free-electrons.com/source/include/uapi/linux/if.h#L70
void print_if_flags(int if_flags)
{
	int i;
	int found;
	const char *name;
	for (i = 0; i < 32; i++) {
		found = 1;
		switch (if_flags & (1 << i)) {
		case IFF_UP:
			name = "IFF_UP";
			break;
		case IFF_BROADCAST:
			name = "IFF_BROADCAST";
			break;
		case IFF_DEBUG:
			name = "IFF_DEBUG";
			break;
		case IFF_LOOPBACK:
			name = "IFF_LOOPBACK";
			break;
		case IFF_POINTOPOINT:
			name = "IFF_POINTOPOINT";
			break;
		case IFF_NOTRAILERS:
			name = "IFF_NOTRAILERS";
			break;
		case IFF_RUNNING:
			name = "IFF_RUNNING";
			break;
		case IFF_NOARP:
			name = "IFF_NOARP";
			break;
		case IFF_PROMISC:
			name = "IFF_PROMISC";
			break;
		case IFF_ALLMULTI:
			name = "IFF_ALLMULTI";
			break;
		case IFF_MASTER:
			name = "IFF_MASTER";
			break;
		case IFF_SLAVE:
			name = "IFF_SLAVE";
			break;
		case IFF_MULTICAST:
			name = "IFF_MULTICAST";
			break;
		case IFF_PORTSEL:
			name = "IFF_PORTSEL";
			break;
		case IFF_AUTOMEDIA:
			name = "IFF_AUTOMEDIA";
			break;
		case IFF_DYNAMIC:
			name = "IFF_DYNAMIC";
			break;
		//case IFF_LOWER_UP: name = "IFF_LOWER_UP"; break;
		//case IFF_DORMANT: name = "IFF_DORMANT"; break;
		//case IFF_ECHO: name = "IFF_ECHO"; break;
		default:
			found = 0; //printf("not found: %d\n", i);
		}
		if (found) {
			printf("%s%s", (i != 0) ? ", " : "", name);
		}
	}
}

void print_if_info_msg(struct ifinfomsg *msg)
{
	printf("Interface info message:\n");
	printf("    ifi_family: %d\n", msg->ifi_family);
	printf("    ifi_type: %d\n", msg->ifi_type);
	printf("    ifi_index: %d\n", msg->ifi_index);
	printf("    ifi_flags: 0x%x\n", msg->ifi_flags);
	printf("      flags: ");
	print_if_flags(msg->ifi_flags);
	printf("\n");
	printf("    ifi_change: %d\n", msg->ifi_change);
}

void print_rta_type(int type)
{
	switch (type) {
	case IFLA_UNSPEC:
		printf("IFLA_UNSPEC (%d)", IFLA_UNSPEC);
		break;
	case IFLA_ADDRESS:
		printf("IFLA_ADDRESS (%d)", IFLA_ADDRESS);
		break;
	case IFLA_BROADCAST:
		printf("IFLA_BROADCAST (%d)", IFLA_BROADCAST);
		break;
	case IFLA_IFNAME:
		printf("IFLA_IFNAME (%d)", IFLA_IFNAME);
		break;
	case IFLA_MTU:
		printf("IFLA_MTU (%d)", IFLA_MTU);
		break;
	case IFLA_LINK:
		printf("IFLA_LINK (%d)", IFLA_LINK);
		break;
	case IFLA_QDISC:
		printf("IFLA_QDISC (%d)", IFLA_QDISC);
		break;
	case IFLA_STATS:
		printf("IFLA_STATS (%d)", IFLA_STATS);
		break;
	case IFLA_COST:
		printf("IFLA_COST (%d)", IFLA_COST);
		break;
	case IFLA_PRIORITY:
		printf("IFLA_PRIORITY (%d)", IFLA_PRIORITY);
		break;
	case IFLA_MASTER:
		printf("IFLA_MASTER (%d)", IFLA_MASTER);
		break;
	case IFLA_WIRELESS:
		printf("IFLA_WIRELESS (%d)", IFLA_WIRELESS);
		break;
	case IFLA_PROTINFO:
		printf("IFLA_PROTINFO (%d)", IFLA_PROTINFO);
		break;
	case IFLA_TXQLEN:
		printf("IFLA_TXQLEN (%d)", IFLA_TXQLEN);
		break;
	case IFLA_MAP:
		printf("IFLA_MAP (%d)", IFLA_MAP);
		break;
	case IFLA_WEIGHT:
		printf("IFLA_WEIGHT (%d)", IFLA_WEIGHT);
		break;
	case IFLA_OPERSTATE:
		printf("IFLA_OPERSTATE (%d)", IFLA_OPERSTATE);
		break;
	case IFLA_LINKMODE:
		printf("IFLA_LINKMODE (%d)", IFLA_LINKMODE);
		break;
	case IFLA_LINKINFO:
		printf("IFLA_LINKINFO (%d)", IFLA_LINKINFO);
		break;
	case IFLA_NET_NS_PID:
		printf("IFLA_NET_NS_PID (%d)", IFLA_NET_NS_PID);
		break;
	case IFLA_IFALIAS:
		printf("IFLA_IFALIAS (%d)", IFLA_IFALIAS);
		break;
	case IFLA_NUM_VF:
		printf("IFLA_NUM_VF (%d)", IFLA_NUM_VF);
		break;
	case IFLA_VFINFO_LIST:
		printf("IFLA_VFINFO_LIST (%d)", IFLA_VFINFO_LIST);
		break;
	case IFLA_STATS64:
		printf("IFLA_STATS64 (%d)", IFLA_STATS64);
		break;
	case IFLA_VF_PORTS:
		printf("IFLA_VF_PORTS (%d)", IFLA_VF_PORTS);
		break;
	case IFLA_PORT_SELF:
		printf("IFLA_PORT_SELF (%d)", IFLA_PORT_SELF);
		break;
	case IFLA_AF_SPEC:
		printf("IFLA_AF_SPEC (%d)", IFLA_AF_SPEC);
		break;
	case IFLA_GROUP:
		printf("IFLA_GROUP (%d)", IFLA_GROUP);
		break;
	case IFLA_NET_NS_FD:
		printf("IFLA_NET_NS_FD (%d)", IFLA_NET_NS_FD);
		break;
	case IFLA_EXT_MASK:
		printf("IFLA_EXT_MASK (%d)", IFLA_EXT_MASK);
		break;
	case IFLA_PROMISCUITY:
		printf("IFLA_PROMISCUITY (%d)", IFLA_PROMISCUITY);
		break;
	case IFLA_NUM_TX_QUEUES:
		printf("IFLA_NUM_TX_QUEUES (%d)", IFLA_NUM_TX_QUEUES);
		break;
	case IFLA_NUM_RX_QUEUES:
		printf("IFLA_NUM_RX_QUEUES (%d)", IFLA_NUM_RX_QUEUES);
		break;
	case IFLA_CARRIER:
		printf("IFLA_CARRIER (%d)", IFLA_CARRIER);
		break;
	case IFLA_PHYS_PORT_ID:
		printf("IFLA_PHYS_PORT_ID (%d)", IFLA_PHYS_PORT_ID);
		break;
	case IFLA_CARRIER_CHANGES:
		printf("IFLA_CARRIER_CHANGES (%d)", IFLA_CARRIER_CHANGES);
		break;
	}
}

int main()
{
	int rc;

	int sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);

	if (sock < 0) {
		perror("socket()");
		exit(EXIT_FAILURE);
	}
	printf("socket(): %d\n", sock);

	struct sockaddr_nl recv_addr;
	memset(&recv_addr, 0, sizeof(recv_addr));
	recv_addr.nl_family = AF_NETLINK;
	recv_addr.nl_pid = getpid();
	recv_addr.nl_groups = 0;

	rc = bind(sock, (struct sockaddr *)&recv_addr, sizeof(recv_addr));
	if (rc < 0) {
		perror("bind()");
		exit(EXIT_FAILURE);
	}
	printf("bind(): %d\n", rc);

	struct sockaddr_nl dest_addr;
	memset(&dest_addr, 0, sizeof(dest_addr));
	dest_addr.nl_family = AF_NETLINK;
	dest_addr.nl_pid = 0;
	dest_addr.nl_groups = 0;

	struct {
		struct nlmsghdr nh;
		struct ifinfomsg ifmsg;
		char attrbuf[512];
	} req;
	struct rtattr *rta;

	memset(&req, 0, sizeof(req));
	req.nh.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg));
	req.nh.nlmsg_flags = NLM_F_REQUEST;
	req.nh.nlmsg_type = RTM_GETLINK;
	req.ifmsg.ifi_family = AF_UNSPEC;
	req.ifmsg.ifi_index = if_nametoindex("eth0");
	req.ifmsg.ifi_change = 0xffffffff; /* ??? */
	rta = (struct rtattr *)(((char *)&req) + NLMSG_ALIGN(req.nh.nlmsg_len));
	rta->rta_type = IFLA_STATS;
	rta->rta_len = RTA_LENGTH(sizeof(unsigned int));
	req.nh.nlmsg_len = NLMSG_ALIGN(req.nh.nlmsg_len) + RTA_LENGTH(sizeof(struct rtnl_link_stats));
	rc = send(sock, &req, req.nh.nlmsg_len, 0);
	if (rc < 0) {
		perror("send()");
		exit(EXIT_FAILURE);
	}
	printf("send(): %d\n", rc);

	char buf[4096];
	rc = recv(sock, &buf, 4096, 0);
	if (rc < 0) {
		perror("recv()");
		exit(EXIT_FAILURE);
	}
	printf("recv(): %d\n", rc);

	printf("=====================================================================\n");
	DumpHex(buf, rc);
	printf("=====================================================================\n");

	struct nlmsghdr *recv_hdr = (struct nlmsghdr *)buf;
	print_netlink_header(recv_hdr);

	struct ifinfomsg *infomsg = NLMSG_DATA(recv_hdr);
	print_if_info_msg(infomsg);

	rta = IFLA_RTA(infomsg);
	int len = recv_hdr->nlmsg_len;

	printf("Routing attributes:\n");
	while (RTA_OK(rta, len)) {
		printf("    ");
		print_rta_type(rta->rta_type);
		printf("\n");
		if (rta->rta_type == IFLA_IFNAME) {
			char *ifname = RTA_DATA(rta);
			printf("      IFNAME %s\n", ifname);
		} else if (rta->rta_type == IFLA_STATS) {
			struct rtnl_link_stats *stats = RTA_DATA(rta);
			printf("      RX bytes: %u MB\n", stats->rx_bytes / 1024 / 1024);
			printf("      TX bytes: %u MB\n", stats->tx_bytes / 1024 / 1024);
		} else if (rta->rta_type == IFLA_STATS64) {
			struct rtnl_link_stats64 *stats64 = RTA_DATA(rta);
			printf("      64 RX bytes: %llu MB\n", stats64->rx_bytes / 1024 / 1024);
			printf("      64 TX bytes: %llu MB\n", stats64->tx_bytes / 1024 / 1024);
		}
		rta = RTA_NEXT(rta, len);
	}

	close(sock);
	exit(EXIT_SUCCESS);
}